package com.finance.system.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;


public class Client {

    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
    ByteBuffer readBuffer = ByteBuffer.allocate(1024);

    public void start() throws IOException {
        // 打开socket通道
        SocketChannel sc = SocketChannel.open();
        //设置为非阻塞
        sc.configureBlocking(false);
        //连接服务器地址和端口
        sc.connect(new InetSocketAddress("localhost", 8001));
        //打开选择器
        Selector selector = Selector.open();
        //注册连接服务器socket的动作
        sc.register(selector, SelectionKey.OP_CONNECT);

        Scanner scanner = new Scanner(System.in);
        while (true) {
            //选择一组键，其相应的通道已为 I/O 操作准备就绪。
            //此方法执行处于阻塞模式的选择操作。
            selector.select();
            //返回此选择器的已选择键集。
            Set<SelectionKey> keys = selector.selectedKeys();
            System.out.println("keys=" + keys.size());
            Iterator<SelectionKey> keyIterator = keys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove();
                // 判断此通道上是否正在进行连接操作。
                if (key.isConnectable()) {
                    sc.finishConnect();
                    sc.register(selector, SelectionKey.OP_WRITE);
                    System.out.println("server connected...");
                    break;
                } else if (key.isWritable()) { //写数据
                    System.out.print("please input message:");
                    String message = scanner.nextLine();
                    String[] messageList = message.split(",");
                    //ByteBuffer writeBuffer = ByteBuffer.wrap(message.getBytes());
                    ByteBuffer byteBuffer = ByteBuffer.allocate(500);

                    for (String s : messageList) {
                        //组装消息为 4 字节header + length 字节消息体
                        byte[] bodyBytes = s.getBytes();
                        byte[] headBytes = intToBytes(bodyBytes.length);
                        byteBuffer.put(headBytes);
                        byteBuffer.put(bodyBytes);
                    }


                    //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
                    byteBuffer.flip();
                    sc.write(byteBuffer);

                    //注册写操作,每个chanel只能注册一个操作，最后注册的一个生效
                    //如果你对不止一种事件感兴趣，那么可以用“位或”操作符将常量连接起来
                    //int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
                    //使用interest集合
                    sc.register(selector, SelectionKey.OP_READ);

                } else if (key.isReadable()){//读取数据
                    System.out.print("receive message:");
                    SocketChannel client = (SocketChannel) key.channel();
                    client.read(readBuffer);
                    //将缓冲区清空以备下次读取
                    System.out.println(new String(ByteBufferUtils.readBytes(readBuffer)));
                    readBuffer.clear();

                    //注册读操作，下一次读取
                    sc.register(selector, SelectionKey.OP_WRITE);
                }
            }
        }
    }


    /**
     * 测试半包、粘包的用例
     * 半包：由于服务端每次读取100个字节，客户端每次发送102个字节时，服务端需要读两次。
     * 粘包：由于服务端每次读取100个字节，客户端发送超过100个字节时，服务端需要第二次读，当第一次读时，让客户端再发送 102个字节，那服务端读到的将是 上个包剩余的2个字节 +
     * 下个包的98个字节，其中第3-7字节是下个包的头，服务端最后还需要再读4个字节结束
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        new Client().start();
    }

    public static byte[] intToBytes(int value) {
        byte[] result = new byte[4];
        // 由高位到低位
        result[0] = (byte) ((value >> 24) & 0xFF);
        result[1] = (byte) ((value >> 16) & 0xFF);
        result[2] = (byte) ((value >> 8) & 0xFF);
        result[3] = (byte) (value & 0xFF);
        return result;
    }

}